﻿using System;
using System.IO;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using TNet;
using UnityEngine;

[AddComponentMenu("TNet/Network Manager")]
public class TNManager : MonoBehaviour
{
    private GameClient mClient = new GameClient();
    private static TNManager mInstance;
    private bool mJoiningChannel;
    private static int mObjectOwner = -1;
    private static Player mPlayer = new Player("Guest");
    private static List<Player> mPlayers;
    private static List<CachedFunc> mRCCs = new List<CachedFunc>();
    public GameObject[] objects;
    public static TNet.GameClient.OnPlayerSync onPlayerSync;
    public static bool rebuildMethodList = true;

    public static void AddRCCs<T>()
    {
        AddRCCs(null, typeof(T));
    }

    public static void AddRCCs(MonoBehaviour mb)
    {
        AddRCCs(mb, mb.GetType());
    }

    private static void AddRCCs(object obj, System.Type type)
    {
        MethodInfo[] methods = type.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
        for (int i = 0; i < methods.Length; i++)
        {
            if (methods[i].IsDefined(typeof(RCC), true))
            {
                RCC rcc = (RCC) methods[i].GetCustomAttributes(typeof(RCC), true)[0];
                for (int j = 0; j < mRCCs.size; j++)
                {
                    CachedFunc func = mRCCs[j];
                    if (func.id == rcc.id)
                    {
                        func.obj = obj;
                        func.func = methods[i];
                        return;
                    }
                }
                CachedFunc item = new CachedFunc {
                    obj = obj,
                    func = methods[i],
                    id = rcc.id
                };
                mRCCs.Add(item);
            }
        }
    }

    private void Awake()
    {
        if (mInstance != null)
        {
            UnityEngine.Object.Destroy(base.gameObject);
        }
        else
        {
            mInstance = this;
            rebuildMethodList = true;
            UnityEngine.Object.DontDestroyOnLoad(base.gameObject);
            this.mClient.onError = new TNet.GameClient.OnError(this.OnError);
            this.mClient.onConnect = new TNet.GameClient.OnConnect(this.OnConnect);
            this.mClient.onDisconnect = new TNet.GameClient.OnDisconnect(this.OnDisconnect);
            this.mClient.onJoinChannel = new TNet.GameClient.OnJoinChannel(this.OnJoinChannel);
            this.mClient.onLeftChannel = new TNet.GameClient.OnLeftChannel(this.OnLeftChannel);
            this.mClient.onLoadLevel = new TNet.GameClient.OnLoadLevel(this.OnLoadLevel);
            this.mClient.onPlayerJoined = new TNet.GameClient.OnPlayerJoined(this.OnPlayerJoined);
            this.mClient.onPlayerLeft = new TNet.GameClient.OnPlayerLeft(this.OnPlayerLeft);
            this.mClient.onPlayerSync = new TNet.GameClient.OnPlayerSync(this.OnPlayerSync);
            this.mClient.onRenamePlayer = new TNet.GameClient.OnRenamePlayer(this.OnRenamePlayer);
            this.mClient.onCreate = new GameClient.OnCreate(this.OnCreateObject);
            this.mClient.onDestroy = new TNet.GameClient.OnDestroy(this.OnDestroyObject);
            this.mClient.onForwardedPacket = new TNet.GameClient.OnForwardedPacket(this.OnForwardedPacket);
        }
    }

    public static BinaryWriter BeginSend(byte packetID)
    {
        return mInstance.mClient.BeginSend(packetID);
    }

    public static BinaryWriter BeginSend(Packet type)
    {
        return mInstance.mClient.BeginSend(type);
    }

    public static void CloseChannel()
    {
        if (mInstance != null)
        {
            mInstance.mClient.CloseChannel();
        }
    }

    public static void Connect(string address)
    {
        char[] separator = new char[] { ':' };
        string[] strArray = address.Split(separator);
        int result = 0x1407;
        if (strArray.Length > 1)
        {
            int.TryParse(strArray[1], out result);
        }
        Connect(strArray[0], result);
    }

    public static void Connect(IPEndPoint externalIP, IPEndPoint internalIP)
    {
        instance.mClient.Disconnect();
        instance.mClient.playerName = mPlayer.name;
        instance.mClient.playerData = mPlayer.data;
        instance.mClient.Connect(externalIP, internalIP);
    }

    public static void Connect(string address, int port)
    {
        IPEndPoint externalIP = Tools.ResolveEndPoint(address, port);
        if (externalIP == null)
        {
            instance.OnConnect(false, "Unable to resolve [" + address + "]");
        }
        else
        {
            instance.mClient.playerName = mPlayer.name;
            instance.mClient.playerData = mPlayer.data;
            instance.mClient.Connect(externalIP, null);
        }
    }

    public static void Create(string path, bool persistent = true)
    {
        CreateEx(0, persistent, path, new object[0]);
    }

    public static void Create(GameObject go, bool persistent = true)
    {
        CreateEx(0, persistent, go, new object[0]);
    }

    public static void Create(string path, Vector3 pos, Quaternion rot, bool persistent = true)
    {
        object[] objs = new object[] { pos, rot };
        CreateEx(1, persistent, path, objs);
    }

    public static void Create(GameObject go, Vector3 pos, Quaternion rot, bool persistent = true)
    {
        object[] objs = new object[] { pos, rot };
        CreateEx(1, persistent, go, objs);
    }

    public static void Create(string path, Vector3 pos, Quaternion rot, Vector3 vel, Vector3 angVel, bool persistent = true)
    {
        object[] objs = new object[] { pos, rot, vel, angVel };
        CreateEx(2, persistent, path, objs);
    }

    public static void Create(GameObject go, Vector3 pos, Quaternion rot, Vector3 vel, Vector3 angVel, bool persistent = true)
    {
        object[] objs = new object[] { pos, rot, vel, angVel };
        CreateEx(2, persistent, go, objs);
    }

    public static void CreateChannel(string levelName, bool persistent, int playerLimit, string password)
    {
        if (mInstance != null)
        {
            mInstance.mJoiningChannel = true;
            mInstance.mClient.JoinChannel(-1, levelName, persistent, playerLimit, password);
        }
        else
        {
            Application.LoadLevel(levelName);
        }
    }

    public static void CreateEx(int rccID, bool persistent, string path, params object[] objs)
    {
        GameObject go = LoadGameObject(path);
        if (go != null)
        {
            if (isConnected)
            {
                BinaryWriter bw = mInstance.mClient.BeginSend(Packet.RequestCreate);
                byte flag = GetFlag(go, persistent);
                bw.Write((ushort) 0xffff);
                bw.Write(flag);
                bw.Write(path);
                bw.Write((byte) rccID);
                bw.WriteArray(objs);
                EndSend();
            }
            else
            {
                objs = BinaryExtensions.CombineArrays(go, objs);
                UnityTools.ExecuteAll(GetRCCs(), (byte) rccID, objs);
                UnityTools.Clear(objs);
            }
        }
        else
        {
            Debug.LogError("Unable to load " + path);
        }
    }

    public static void CreateEx(int rccID, bool persistent, GameObject go, params object[] objs)
    {
        if (go != null)
        {
            int index = IndexOf(go);
            if (isConnected)
            {
                if (index != -1)
                {
                    BinaryWriter bw = mInstance.mClient.BeginSend(Packet.RequestCreate);
                    bw.Write((ushort) index);
                    bw.Write(GetFlag(go, persistent));
                    bw.Write((byte) rccID);
                    bw.WriteArray(objs);
                    EndSend();
                    return;
                }
                Debug.LogError("\"" + go.name + "\" has not been added to TNManager's list of objects, so it cannot be instantiated.\nConsider placing it into the Resources folder and passing its name instead.", go);
            }
            objs = BinaryExtensions.CombineArrays(go, objs);
            UnityTools.ExecuteAll(GetRCCs(), (byte) rccID, objs);
            UnityTools.Clear(objs);
        }
    }

    private static GameObject CreateGameObject(GameObject prefab, BinaryReader reader)
    {
        object obj2;
        if (prefab == null)
        {
            return null;
        }
        byte funcID = reader.ReadByte();
        if (funcID == 0)
        {
            return (UnityEngine.Object.Instantiate(prefab) as GameObject);
        }
        object[] parameters = reader.ReadArray(prefab);
        if (!UnityTools.ExecuteFirst(GetRCCs(), funcID, out obj2, parameters))
        {
            Debug.LogError("[TNet] Failed to call RCC #" + funcID + ".\nDid you forget to register it in Awake() via TNManager.AddRCCs?");
            UnityTools.Clear(parameters);
            return null;
        }
        UnityTools.Clear(parameters);
        if (obj2 == null)
        {
            Debug.LogError(string.Concat(new object[] { "[TNet] Instantiating \"", prefab.name, "\" via RCC #", funcID, " returned null.\nDid you forget to return the game object from your RCC?" }));
        }
        return (obj2 as GameObject);
    }

    public static void Destroy(GameObject go)
    {
        if (isConnected)
        {
            TNObject component = go.GetComponent<TNObject>();
            if (component != null)
            {
                mInstance.mClient.BeginSend(Packet.RequestDestroy).Write(component.uid);
                mInstance.mClient.EndSend();
                return;
            }
        }
        UnityEngine.Object.Destroy(go);
    }

    public static void Disconnect()
    {
        if (mInstance != null)
        {
            mInstance.mClient.Disconnect();
        }
    }

    public static void EndSend()
    {
        mInstance.mClient.EndSend(true);
    }

    public static void EndSend(bool reliable)
    {
        mInstance.mClient.EndSend(reliable);
    }

    public static void EndSend(int port)
    {
        mInstance.mClient.EndSend(port);
    }

    public static void EndSend(IPEndPoint target)
    {
        mInstance.mClient.EndSend(target);
    }

    private static byte GetFlag(GameObject go, bool persistent)
    {
        if (go.GetComponent<TNObject>() == null)
        {
            return 0;
        }
        return (!persistent ? ((byte) 2) : ((byte) 1));
    }

    public static Player GetPlayer(int id)
    {
        if (isConnected)
        {
            return mInstance.mClient.GetPlayer(id);
        }
        if (id == mPlayer.id)
        {
            return mPlayer;
        }
        return null;
    }

    public static List<CachedFunc> GetRCCs()
    {
        if (rebuildMethodList)
        {
            rebuildMethodList = false;
            if (mInstance != null)
            {
                MonoBehaviour[] componentsInChildren = mInstance.GetComponentsInChildren<MonoBehaviour>();
                int index = 0;
                int length = componentsInChildren.Length;
                while (index < length)
                {
                    MonoBehaviour behaviour = componentsInChildren[index];
                    AddRCCs(behaviour, behaviour.GetType());
                    index++;
                }
            }
            else
            {
                AddRCCs<TNManager>();
            }
        }
        return mRCCs;
    }

    public static int IndexOf(GameObject go)
    {
        if (((go != null) && (mInstance != null)) && (mInstance.objects != null))
        {
            int index = 0;
            int length = mInstance.objects.Length;
            while (index < length)
            {
                if (mInstance.objects[index] == go)
                {
                    return index;
                }
                index++;
            }
            Debug.LogError("[TNet] The game object was not found in the TNManager's list of objects. Did you forget to add it?", go);
        }
        return -1;
    }

    public static void JoinChannel(int channelID, string levelName)
    {
        if (mInstance != null)
        {
            mInstance.mJoiningChannel = true;
            mInstance.mClient.JoinChannel(channelID, levelName, false, 0xffff, null);
        }
        else
        {
            Application.LoadLevel(levelName);
        }
    }

    public static void JoinChannel(int channelID, string levelName, bool persistent, int playerLimit, string password)
    {
        if (mInstance != null)
        {
            mInstance.mJoiningChannel = true;
            mInstance.mClient.JoinChannel(channelID, levelName, persistent, playerLimit, password);
        }
        else
        {
            Application.LoadLevel(levelName);
        }
    }

    public static void JoinRandomChannel(string levelName, bool persistent, int playerLimit, string password)
    {
        if (mInstance != null)
        {
            mInstance.mJoiningChannel = true;
            mInstance.mClient.JoinChannel(-2, levelName, persistent, playerLimit, password);
        }
    }

    public static void LeaveChannel()
    {
        if (mInstance != null)
        {
            mInstance.mClient.LeaveChannel();
        }
    }

    public static void LoadFile(string filename, GameClient.OnLoadFile callback)
    {
        if (callback != null)
        {
            if (isConnected)
            {
                mInstance.mClient.LoadFile(filename, callback);
            }
            else
            {
                callback(filename, Tools.ReadFile(filename));
            }
        }
    }

    private static GameObject LoadGameObject(string path)
    {
        if (string.IsNullOrEmpty(path))
        {
            Debug.LogError("[TNet] Null path passed to TNManager.LoadGameObject!");
            return null;
        }
        GameObject obj2 = Resources.Load(path, typeof(GameObject)) as GameObject;
        if (obj2 == null)
        {
            Debug.LogError("[TNet] Attempting to create a game object that can't be found in the Resources folder: " + path);
        }
        return obj2;
    }

    public static void LoadLevel(string levelName)
    {
        if (isConnected)
        {
            mInstance.mClient.LoadLevel(levelName);
        }
        else
        {
            Application.LoadLevel(levelName);
        }
    }

    private void OnConnect(bool success, string message)
    {
        object[] parameters = new object[] { success, message };
        UnityTools.Broadcast("OnNetworkConnect", parameters);
    }

    [RCC(0)]
    private static GameObject OnCreate0(GameObject go)
    {
        return (UnityEngine.Object.Instantiate(go) as GameObject);
    }

    [RCC(1)]
    private static GameObject OnCreate1(GameObject go, Vector3 pos, Quaternion rot)
    {
        return (UnityEngine.Object.Instantiate(go, pos, rot) as GameObject);
    }

    [RCC(2)]
    private static GameObject OnCreate2(GameObject go, Vector3 pos, Quaternion rot, Vector3 velocity, Vector3 angularVelocity)
    {
        return UnityTools.Instantiate(go, pos, rot, velocity, angularVelocity);
    }

    private void OnCreateObject(int creator, int index, uint objectID, BinaryReader reader)
    {
        mObjectOwner = creator;
        GameObject prefab = null;
        if (index == 0xffff)
        {
            prefab = LoadGameObject(reader.ReadString());
        }
        else if ((index >= 0) && (index < this.objects.Length))
        {
            prefab = this.objects[index];
        }
        else
        {
            Debug.LogError("Attempting to create an invalid object. Index: " + index);
            return;
        }
        prefab = CreateGameObject(prefab, reader);
        if ((prefab != null) && (objectID != 0))
        {
            TNObject component = prefab.GetComponent<TNObject>();
            if (component != null)
            {
                component.uid = objectID;
                component.Register();
            }
            else
            {
                Debug.LogWarning("[TNet] The instantiated object has no TNObject component. Don't request an ObjectID when creating it.", prefab);
            }
        }
        mObjectOwner = -1;
    }

    private void OnDestroy()
    {
        if (mInstance == this)
        {
            if (isConnected)
            {
                this.mClient.Disconnect();
            }
            this.mClient.StopUDP();
            mInstance = null;
        }
    }

    private void OnDestroyObject(uint objID)
    {
        TNObject obj2 = TNObject.Find(objID);
        if (obj2 != null)
        {
            UnityEngine.Object.Destroy(obj2.gameObject);
        }
    }

    private void OnDisconnect()
    {
        this.mJoiningChannel = false;
        UnityTools.Broadcast("OnNetworkDisconnect", new object[0]);
    }

    private void OnError(string err)
    {
        object[] parameters = new object[] { err };
        UnityTools.Broadcast("OnNetworkError", parameters);
    }

    private void OnForwardedPacket(BinaryReader reader)
    {
        uint num;
        byte num2;
        TNObject.DecodeUID(reader.ReadUInt32(), out num, out num2);
        if (num2 == 0)
        {
            TNObject.FindAndExecute(num, reader.ReadString(), reader.ReadArray());
        }
        else
        {
            TNObject.FindAndExecute(num, num2, reader.ReadArray());
        }
    }

    private void OnJoinChannel(bool success, string message)
    {
        this.mJoiningChannel = false;
        object[] parameters = new object[] { success, message };
        UnityTools.Broadcast("OnNetworkJoinChannel", parameters);
    }

    private void OnLeftChannel()
    {
        this.mJoiningChannel = false;
        UnityTools.Broadcast("OnNetworkLeaveChannel", new object[0]);
    }

    private void OnLoadLevel(string levelName)
    {
        if (!string.IsNullOrEmpty(levelName))
        {
            Application.LoadLevel(levelName);
        }
    }

    private void OnPlayerJoined(Player p)
    {
        object[] parameters = new object[] { p };
        UnityTools.Broadcast("OnNetworkPlayerJoin", parameters);
    }

    private void OnPlayerLeft(Player p)
    {
        object[] parameters = new object[] { p };
        UnityTools.Broadcast("OnNetworkPlayerLeave", parameters);
    }

    private void OnPlayerSync(Player p)
    {
        if (onPlayerSync != null)
        {
            onPlayerSync(p);
        }
    }

    private void OnRenamePlayer(Player p, string previous)
    {
        mPlayer.name = p.name;
        object[] parameters = new object[] { p, previous };
        UnityTools.Broadcast("OnNetworkPlayerRenamed", parameters);
    }

    public static void Ping(IPEndPoint udpEndPoint, GameClient.OnPing callback)
    {
        instance.mClient.Ping(udpEndPoint, callback);
    }

    private static void RemoveRCC(int rccID)
    {
        for (int i = 0; i < mRCCs.size; i++)
        {
            CachedFunc func = mRCCs[i];
            if (func.id == rccID)
            {
                mRCCs.RemoveAt(i);
                return;
            }
        }
    }

    private static void RemoveRCCs<T>()
    {
        MethodInfo[] methods = typeof(T).GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
        for (int i = 0; i < methods.Length; i++)
        {
            if (methods[i].IsDefined(typeof(RCC), true))
            {
                RCC rcc = (RCC) methods[i].GetCustomAttributes(typeof(RCC), true)[0];
                RemoveRCC(rcc.id);
            }
        }
    }

    public static void SaveFile(string filename, byte[] data)
    {
        if (isConnected)
        {
            mInstance.mClient.SaveFile(filename, data);
        }
        else
        {
            try
            {
                Tools.WriteFile(filename, data);
            }
            catch (Exception exception)
            {
                Debug.LogError(exception.Message + " (" + filename + ")");
            }
        }
    }

    public static void SetHost(Player player)
    {
        if (mInstance != null)
        {
            mInstance.mClient.SetHost(player);
        }
    }

    public static void SetPacketHandler(byte packetID, GameClient.OnPacket callback)
    {
        instance.mClient.packetHandlers[packetID] = callback;
    }

    public static void SetPacketHandler(Packet packet, GameClient.OnPacket callback)
    {
        instance.mClient.packetHandlers[(byte) packet] = callback;
    }

    public static void SetPlayerLimit(int max)
    {
        if (mInstance != null)
        {
            mInstance.mClient.SetPlayerLimit(max);
        }
    }

    public static void SetTimeout(int seconds)
    {
        if (mInstance != null)
        {
            mInstance.mClient.SetTimeout(seconds);
        }
    }

    public static bool StartUDP(int udpPort)
    {
        return instance.mClient.StartUDP(udpPort);
    }

    public static void StopUDP()
    {
        if (mInstance != null)
        {
            mInstance.mClient.StopUDP();
        }
    }

    public static void SyncPlayerData()
    {
        if (isConnected)
        {
            mInstance.mClient.SyncPlayerData();
        }
        if (onPlayerSync != null)
        {
            onPlayerSync(player);
        }
    }

    private void Update()
    {
        this.mClient.ProcessPackets();
    }

    public static bool canUseUDP
    {
        get
        {
            return ((mInstance != null) && mInstance.mClient.canUseUDP);
        }
    }

    public static string channelData
    {
        get
        {
            return ((mInstance == null) ? string.Empty : mInstance.mClient.channelData);
        }
        set
        {
            if (mInstance != null)
            {
                mInstance.mClient.channelData = value;
            }
        }
    }

    public static int channelID
    {
        get
        {
            return (!isConnected ? 0 : mInstance.mClient.channelID);
        }
    }

    public static GameClient client
    {
        get
        {
            return ((mInstance == null) ? null : mInstance.mClient);
        }
    }

    public static int hostID
    {
        get
        {
            return (!isConnected ? mPlayer.id : mInstance.mClient.hostID);
        }
    }

    private static TNManager instance
    {
        get
        {
            if (mInstance == null)
            {
                mInstance = new GameObject("Network Manager").AddComponent<TNManager>();
            }
            return mInstance;
        }
    }

    public static bool isActive
    {
        get
        {
            return ((mInstance != null) && mInstance.mClient.isActive);
        }
        set
        {
            if (mInstance != null)
            {
                mInstance.mClient.isActive = value;
            }
        }
    }

    public static bool isConnected
    {
        get
        {
            return ((mInstance != null) && mInstance.mClient.isConnected);
        }
    }

    public static bool isHosting
    {
        get
        {
            return ((mInstance == null) || mInstance.mClient.isHosting);
        }
    }

    public static bool isInChannel
    {
        get
        {
            return (((mInstance != null) && mInstance.mClient.isConnected) && mInstance.mClient.isInChannel);
        }
    }

    public static bool isJoiningChannel
    {
        get
        {
            return ((mInstance != null) && mInstance.mJoiningChannel);
        }
    }

    public static bool isThisMyObject
    {
        get
        {
            return (objectOwnerID == playerID);
        }
    }

    public static bool isTryingToConnect
    {
        get
        {
            return ((mInstance != null) && mInstance.mClient.isTryingToConnect);
        }
    }

    public static int listeningPort
    {
        get
        {
            return ((mInstance == null) ? 0 : mInstance.mClient.listeningPort);
        }
    }

    public static bool noDelay
    {
        get
        {
            return ((mInstance != null) && mInstance.mClient.noDelay);
        }
        set
        {
            if (mInstance != null)
            {
                mInstance.mClient.noDelay = value;
            }
        }
    }

    public static int objectOwnerID
    {
        get
        {
            return ((mObjectOwner != -1) ? mObjectOwner : hostID);
        }
    }

    public static IPEndPoint packetSource
    {
        get
        {
            return ((mInstance == null) ? null : mInstance.mClient.packetSource);
        }
    }

    public static int ping
    {
        get
        {
            return ((mInstance == null) ? 0 : mInstance.mClient.ping);
        }
    }

    public static Player player
    {
        get
        {
            return (!isConnected ? mPlayer : mInstance.mClient.player);
        }
    }

    public static object playerData
    {
        get
        {
            return (!isConnected ? mPlayer.data : mInstance.mClient.playerData);
        }
        set
        {
            mPlayer.data = value;
            if (isConnected)
            {
                mInstance.mClient.playerData = value;
            }
        }
    }

    public static DataNode playerDataNode
    {
        get
        {
            DataNode playerData = TNManager.playerData as DataNode;
            if (playerData == null)
            {
                playerData = new DataNode("Version", 12);
                TNManager.playerData = playerData;
            }
            return playerData;
        }
    }

    public static int playerID
    {
        get
        {
            return (!isConnected ? mPlayer.id : mInstance.mClient.playerID);
        }
    }

    public static string playerName
    {
        get
        {
            return (!isConnected ? mPlayer.name : mInstance.mClient.playerName);
        }
        set
        {
            if (playerName != value)
            {
                mPlayer.name = value;
                if (isConnected)
                {
                    mInstance.mClient.playerName = value;
                }
            }
        }
    }

    public static List<Player> players
    {
        get
        {
            if (isConnected)
            {
                return mInstance.mClient.players;
            }
            if (mPlayers == null)
            {
                mPlayers = new List<Player>();
            }
            return mPlayers;
        }
    }

    public static long serverTime
    {
        get
        {
            return ((mInstance == null) ? (DateTime.UtcNow.Ticks / 0x2710L) : mInstance.mClient.serverTime);
        }
    }
}

